package com.zhuangjie.security.utils;


import io.jsonwebtoken.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.annotation.PostConstruct;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 关于JWT操作的工具类
 */

/**
 * rsaconfig
 * 在这里简化RSA的使用，将公钥和私钥写入到配置文件中，不再生成新的公钥和私钥
 *
 * @author zhuangjie
 * @date 2023/07/01
 */
@Configuration
@PropertySource("classpath:rsa.properties")
@ConfigurationProperties(prefix = "rsa")
@Data
class RSAConfig {

    private String privateStr;
    private String publishStr;


    @PostConstruct
    public void initConfig() {
        // 向需要的类主动注入，这样使用的类就可以不需要成为Bean对象了
        JwtUtils.rsaConfig = this;
    }
}
@Slf4j
public class JwtUtils {
    /**
     * rsa配置
     * 当前包下可见
     */
    static RSAConfig rsaConfig;


    /**
     * 生成token
     *
     * @param claims           存入到token中的数据
     * @param time             token的有效期时间
     * @param timeUnit         time的单位
     * @return {@link String}
     */
    public static String createToken(Map<String, Object> claims, int time, TimeUnit timeUnit) {
        if (rsaConfig.getPrivateStr() == null) {
            log.error("你未配置私钥，无法使用 createToken ！");
            return null;
        }
        Map<String, Object> header = new HashMap<String, Object>();
        header.put(JwsHeader.TYPE, JwsHeader.JWT_TYPE);
        header.put(JwsHeader.ALGORITHM, "RS256");

        try {
            PrivateKey privateKey = RSAUtils.privateKeyFromString(rsaConfig.getPrivateStr());
            // 生成token
            return Jwts.builder()
                    .setHeader(header)  //header，可省略
                    .setClaims(claims) //payload，存放数据的位置，不能放置敏感数据，如：密码等
                    .signWith(SignatureAlgorithm.RS256,privateKey) //通过RSA的私钥加密
                    .setExpiration(new Date(System.currentTimeMillis()+timeUnit.toMillis(time))) //设置过期时间，单位：小时
                    .compact();
        } catch (Exception e) {
            log.error("创建令牌时发生错误：{}", e.getMessage());
            return null;
        }
    }

    /**
     * 校验token
     *
     * @param token 要校验的token
     * @return token中的数据，如果token失效或非法，返回null
     * @throws Exception 如果校验过程中出现错误
     */
    public static Map<String, Object> checkToken(String token) throws Exception {
        if (rsaConfig.getPublishStr() == null) {
            throw new Exception("未配置公钥，无法使用 checkToken！");
        }

        PublicKey publicKey = RSAUtils.publicKeyFromString(rsaConfig.getPublishStr());

        try {
            // 通过token解析数据
            return Jwts.parser()
                    .setSigningKey(publicKey)  // 通过公钥校验
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException e) {
            throw new Exception("令牌已过期！");
        } catch (SignatureException e) {
            throw new IllegalArgumentException("令牌不合法！");
        } catch (Exception e) {
            throw new Exception("令牌校验失败！", e);
        }
    }

}
